/*
 * HTMLCLIP.C
 *
 * Copy HTML text onto the clipboard in HTML formatting for use in applications
 * that support rich text.
 *
 * Copyright (c) 2015-2016 Malcolm J. Smith
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#define WIN32_LEAN_AND_MEAN 1
#define _CRT_SECURE_NO_WARNINGS 1
#define _CRT_NONSTDC_NO_WARNINGS 1
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>

#ifdef MINICRT
#include <minicrt.h>
#endif

#ifndef sprintf_s
#define sprintf_s _snprintf
#endif

#ifndef FILE_SHARE_DELETE
#define FILE_SHARE_DELETE 4
#endif

LPSTR strUsage =
        "Usage:\n"
        "  htmlclip [filename]\n"
        "\n"
        " Copies either the specified file or standard input onto the clipboard as\n"
        " formatted HTML.\n";

BOOL Usage()
{
    ULONG BytesTransferred;
    CHAR Line[80];
    ULONG CVer;

#ifdef _MSC_VER
    CVer = _MSC_VER;
#else
    CVer = 0;
#endif

    sprintf_s(Line, sizeof(Line), "Htmlclip version %i.%i, compiled %s, %s, C version %i.%02i\n\n",
            HTMLCLIP_VER_MAJOR,
            HTMLCLIP_VER_MINOR,
            __DATE__,
#ifdef MINICRT
            "Minicrt",
#else
            "Msvcrt",
#endif
            CVer / 100,
            CVer % 100);

    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), Line, strlen(Line), &BytesTransferred, NULL);
    WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), strUsage, strlen(strUsage), &BytesTransferred, NULL);
    return TRUE;
}

//
//  Dummy header.  0.9 is part of the protocol.  The rest contain strings
//  that are 8 characters long that can be overwritten with real zero padded
//  strings.  The zero padding helps keep computation sane.
//

const char DummyHeader[] = 
                  "Version:0.9\n"
                  "StartHTML:12345678\n"
                  "EndHTML:12345678\n"
                  "StartFragment:12345678\n"
                  "EndFragment:12345678\n";

#define HTMLCLIP_HDR_SIZE (sizeof(DummyHeader)-1)

const char DummyFragStart[] = 
                  "<!--StartFragment-->";

#define HTMLCLIP_FRAGSTART_SIZE (sizeof(DummyFragStart)-1)

const char DummyFragEnd[] = 
                  "<!--EndFragment-->";

#define HTMLCLIP_FRAGEND_SIZE (sizeof(DummyFragEnd)-1)

#define MAX_PIPE_SIZE (4*1024*1024)

int __cdecl
main (
    int argc,
    char* argv[]
    )
{
    HANDLE hFile;
    DWORD  FileSize;
    DWORD  AllocSize;
    HANDLE hMem;
    PUCHAR pMem;
    ULONG  BytesTransferred;
    ULONG  CurrentOffset;
    UINT   ClipFmt;

    //
    //  If the user specified a file, go read it.  If not, read
    //  from standard input.
    //

    if (argc > 1) {

        if (argv[1][0] == '-' || argv[1][0] == '/') {
            Usage();
            return EXIT_FAILURE;
        }

        hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
            printf("Could not open file, error %i\n", GetLastError());
            return EXIT_FAILURE;
        }
    
        FileSize = GetFileSize(hFile, NULL);

    } else {
        DWORD FileType;

        FileSize = MAX_PIPE_SIZE;
        hFile = GetStdHandle(STD_INPUT_HANDLE);

        //
        //  As a bit of a hack, if the input is stdin and stdin is to
        //  a console (as opposed to a pipe or file) assume the user isn't
        //  sure how to run this program and help them along.
        //

        FileType = GetFileType(hFile);
        FileType = FileType & ~(FILE_TYPE_REMOTE);
        if (FileType == FILE_TYPE_CHAR) {
            Usage();
            return EXIT_FAILURE;
        }
    }

    //
    //  Allocate space to copy the file or pipe contents.
    //
    
    AllocSize = FileSize + HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE + HTMLCLIP_FRAGEND_SIZE + 2;
    hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, AllocSize);
    if (hMem == NULL) {
        printf("Could not allocate memory, error %i\n", GetLastError());
        return EXIT_FAILURE;
    }

    pMem = GlobalLock(hMem);

    //
    //  Note this is not Unicode.
    //
    //  First, prepare the header.  Leave a magic space at the end of the
    //  header.
    //

    sprintf_s(pMem,
              AllocSize,
              "Version:0.9\n"
              "StartHTML:%08i\n"
              "EndHTML:%08i\n"
              "StartFragment:%08i\n"
              "EndFragment:%08i\n"
              "<!--StartFragment-->"
              " ",
              HTMLCLIP_HDR_SIZE,
              HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE + HTMLCLIP_FRAGEND_SIZE + 1 + FileSize,
              HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE,
              HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE + 1 + FileSize);

    CurrentOffset = 0;

    //
    //  Read text immediately following the header that we just prepared.
    //

    while (ReadFile(hFile, pMem + HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE + 1 + CurrentOffset, FileSize - CurrentOffset, &BytesTransferred, NULL)) {
        CurrentOffset += BytesTransferred;

        //
        //  If we're reading from the pipe and have exceeded our storage,
        //  fail.
        //

        if (CurrentOffset == FileSize &&
            FileSize == MAX_PIPE_SIZE &&
            hFile == GetStdHandle(STD_INPUT_HANDLE)) {

            printf("Pipe data too large for buffer.  Limit is %i bytes\n", MAX_PIPE_SIZE);
            return EXIT_FAILURE;
        }

        if (BytesTransferred == 0) {
            break;
        }
    }

    if (CurrentOffset == 0) {
        Usage();
        return EXIT_FAILURE;
    }

    //
    //  If we don't have as much text as we expected, the header is wrong
    //  and needs to be rewritten with correct values.
    //

    if (CurrentOffset < FileSize) {
        FileSize = CurrentOffset;
        sprintf_s(pMem,
                  AllocSize,
                  "Version:0.9\n"
                  "StartHTML:%08i\n"
                  "EndHTML:%08i\n"
                  "StartFragment:%08i\n"
                  "EndFragment:%08i\n"
                  "<!--StartFragment-->",
               HTMLCLIP_HDR_SIZE,
               HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE + HTMLCLIP_FRAGEND_SIZE + 1 + FileSize,
               HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE,
               HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE + 1 + FileSize);
           
        //
        //  That magic space from above has now been set to NULL by printf.
        //  Put the magic space back.
        //

        pMem[HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE] = ' ';
    }

    //
    //  Fill in the footer of the protocol.
    //

    sprintf_s(pMem + FileSize + HTMLCLIP_HDR_SIZE + HTMLCLIP_FRAGSTART_SIZE + 1,
              sizeof(DummyFragEnd),
              DummyFragEnd);

    GlobalUnlock(hMem);

    //
    //  Send the buffer to the clipboard.
    //

    if (!OpenClipboard(NULL)) {
        printf("Could not open clipboard, error %i\n", GetLastError());
        return EXIT_FAILURE;
    }

    if (!EmptyClipboard()) {
        printf("Could not empty clipboard, error %i\n", GetLastError());
        return EXIT_FAILURE;
    }

    ClipFmt = RegisterClipboardFormat("HTML Format");
    if (ClipFmt == 0) {
        printf("Could not register clipboard format, error %i\n", GetLastError());
        return EXIT_FAILURE;
    }

    if (SetClipboardData(ClipFmt, hMem) == NULL) {
        printf("Could not set clipboard data, error %i\n", GetLastError());
        return EXIT_FAILURE;
    }

    GlobalFree(hMem);
    if (argc > 1) {
        CloseHandle(hFile);
    }
    return TRUE;
}

// vim:sw=4:ts=4:et:
